package cn.bonoon.kernel.bytecode.asm;

import java.lang.reflect.Method;

import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.context.ApplicationContext;

import cn.bonoon.kernel.annotations.QueryJoin;
import cn.bonoon.kernel.annotations.condition.ConditionOpt;
import cn.bonoon.kernel.bytecode.ASMQueryParser;
import cn.bonoon.kernel.bytecode.QueryConverter;
import cn.bonoon.kernel.descriptors.EntityDescriptor;
import cn.bonoon.kernel.descriptors.QueryDescriptor;
import cn.bonoon.kernel.descriptors.QueryPropertyAccessor;
import cn.bonoon.kernel.query.PageCondition;
import cn.bonoon.kernel.query.Pageable;
import cn.bonoon.kernel.query.SearchCondition;
import cn.bonoon.kernel.util.Opt;
import cn.bonoon.kernel.util.StringHelper;

/**
 * <pre>
 * 
 * </pre>
 * @author jackson
 *
 */
public class ASMQueryBuilder extends ASMAbstractBuilder<QueryConverter, QueryDescriptor>{
	
	private final EntityDescriptor entityDescriptor;
	private final Class<?> parserClass = ASMQueryParser.class;
	
	private final ASMMethod append = new ASMMethod("java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
	private final String[] rules = {"start", "end", "ge","le","gt","lt","eq"};
	private final Opt[] optMaps =  {Opt.GE, Opt.LE, Opt.GE, Opt.LE, Opt.GT, Opt.LT, Opt.EQ};
	private final String parserDes = Type.getInternalName(parserClass);
	private final String iades = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V";
	private final String indes = "(Ljava/lang/String;Ljava/lang/String;)V";
	private final String icdes = "(Ljava/lang/String;Ljava/lang/Object;Z)Z";
	
	public ASMQueryBuilder(ApplicationContext applicationContext, EntityDescriptor entityDescriptor, QueryDescriptor queryDescriptor) {
		super(applicationContext, QueryConverter.class, entityDescriptor.toClass(), queryDescriptor);
		this.entityDescriptor = entityDescriptor;
	}
	
	private String __des(Class<?> cls, String name)throws Exception{
		return Type.getMethodDescriptor(cls.getDeclaredMethod(name));
	}

	private String __des(Class<?> cls, String name, Class<?>... parameterTypes)throws Exception{
		return Type.getMethodDescriptor(cls.getDeclaredMethod(name, parameterTypes));
	}
	
	@Override
	protected void internalBuild() throws Exception {
		//0 -- this
		//1 -- ASMQueryParser
		//2 -- condition
		//3 -- condition type
		MethodVisitor visitor = _visitMethod("init", parserClass, Object.class);
		visitor.visitVarInsn(ALOAD, 2);
		visitor.visitTypeInsn(CHECKCAST, objectPath);
		visitor.visitVarInsn(ASTORE, 3);//把查询对象实体转换对目标类型
		if(objectDescriptor.isAssignable(SearchCondition.class)){
			//处理排序的问题internalOrder
			visitor.visitVarInsn(ALOAD, 1);
			visitor.visitVarInsn(ALOAD, 3);
			visitor.visitMethodInsn(INVOKEVIRTUAL, objectPath, "getSort", __des(SearchCondition.class, "getSort"), false);
			visitor.visitVarInsn(ALOAD, 3);
			visitor.visitMethodInsn(INVOKEVIRTUAL, objectPath, "getOrder", __des(SearchCondition.class, "getOrder"), false);
			visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "internalOrder", __des(parserClass, "internalOrder", String.class, String.class), true);
			if(objectDescriptor.isAssignable(PageCondition.class)){
				visitor.visitVarInsn(ALOAD, 1);
				visitor.visitVarInsn(ALOAD, 3);
				visitor.visitMethodInsn(INVOKEVIRTUAL, objectPath, "toPage", __des(PageCondition.class, "toPage"), false);
				visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "initernalPageable", __des(parserClass, "initernalPageable", Pageable.class), true);
			}
		}
		int vp = 4;//临时变量
		for(QueryPropertyAccessor qpa : objectDescriptor.values()){
			Method getter = qpa.getter();
			String getterDes = Type.getMethodDescriptor(getter);
			ConditionOpt copt = qpa.getAnnotation(ConditionOpt.class);
			String propertyName = qpa.queryProperty();
			Opt opt = Opt.NONE;
			boolean isNullable = false;
			int propertyNotMapped = ICONST_0;
			if(null == copt){
				//进行规则解析
				String name = null;
				if(entityDescriptor.get(propertyName) == null){
					//当这个字段在数据库实体里不存在时，则使用一定的规则来判断该字段是否使用了表达式进行定义
					for(int i = 0; i < rules.length; i++){
						String rule = rules[i];
						if(propertyName.startsWith(rule)){
							name = _0(propertyName, rule.length());
							if(entityDescriptor.get(name) == null){
								if("at".equals(name)){
									//把数据库实体的字段名定义为"at"是不符合命名规范的，虽然语法是正确的
									name = "createAt";
								}else{
									//这个字段没有在数据库实体里定义过，应该被忽略或者抛出异常进行提示
									propertyNotMapped = ICONST_1;
								}
							}
							opt = optMaps[i];
							propertyName = name;
							break;
						}
					}
				}
			}else if(entityDescriptor.get(propertyName) == null){
				propertyNotMapped = ICONST_1;
			}else{
				isNullable = copt.isNull();
				opt = copt.value();
			}
			Class<?> qt = qpa.getType();
			
			if(qt.isPrimitive()){
				int valuePoint = vp++;
				int fieldPoint = vp++;
				_p2o(visitor, getter, getterDes, qt, valuePoint);
				
				Label ifchecked = _checked(visitor, propertyName, valuePoint, propertyNotMapped);
				
				_and(visitor, qpa, propertyName, opt, valuePoint, fieldPoint);
				
				visitor.visitLabel(ifchecked);
			}else if(qt.isArray()){
				//TODO 数组的情况
			}else{
				//读取字段值，存入变量4
				int valuePoint = vp++;
				int fieldPoint = vp++;
				visitor.visitVarInsn(ALOAD, 3);
				visitor.visitMethodInsn(INVOKEVIRTUAL, objectPath, getter.getName(), getterDes, false);
				visitor.visitVarInsn(ASTORE, valuePoint);

				Label ifchecked = _checked(visitor, propertyName, valuePoint, propertyNotMapped);
				
				//if
				Label ifnull = new Label();
				visitor.visitVarInsn(ALOAD, valuePoint);
				visitor.visitJumpInsn(IFNULL, ifnull);
				if(String.class.equals(qt)){
					if(opt == Opt.NONE) opt = Opt.LIKE;
					visitor.visitVarInsn(ALOAD, valuePoint);
					visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "isEmpty", "()Z", false);
					visitor.visitJumpInsn(IFNE, ifnull);

					if(opt == Opt.LIKE){
						_string(visitor, getterDes, "%", "%", valuePoint);
					}else if(opt == Opt.LL){
						_string(visitor, getterDes, "%", null, valuePoint);
					}else if(opt == Opt.RL){
						_string(visitor, getterDes, null, "%", valuePoint);
					}
				}
				
				_and(visitor, qpa, propertyName, opt, valuePoint, fieldPoint);
				
				//else
				if(isNullable){
					Label elsenull = new Label();
					visitor.visitJumpInsn(GOTO, elsenull);
					visitor.visitLabel(ifnull);
					visitor.visitVarInsn(ALOAD, 1);
					visitor.visitVarInsn(ALOAD, fieldPoint);
					visitor.visitLdcInsn(propertyName);
					visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "internalNull", indes, true);
					visitor.visitLabel(elsenull);
				}else{
					visitor.visitLabel(ifnull);
				}
				
				visitor.visitLabel(ifchecked);
			}
		}
		
		_return(visitor, RETURN);
	}

	private Label _checked(MethodVisitor visitor, String propertyName, int valuePoint, int propertyNotMapped) {
		Label ifchecked = new Label();
		visitor.visitVarInsn(ALOAD, 1);
		visitor.visitLdcInsn(propertyName);
		visitor.visitVarInsn(ALOAD, valuePoint);
		visitor.visitInsn(propertyNotMapped);
		visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "internalChecked", icdes, true);
		visitor.visitJumpInsn(IFNE, ifchecked);
		return ifchecked;
	}

	private void _and(MethodVisitor visitor, QueryPropertyAccessor qpa, String propertyName, Opt opt, int valuePoint, int fieldPoint) throws Exception {
		visitor.visitLdcInsn("x");
		visitor.visitVarInsn(ASTORE, fieldPoint);
		QueryJoin[] joins = qpa.getJoins();
		if(null != joins && joins.length > 0){//处理定义了jion连接的情况
			String ijdes = __des(parserClass, "internalJoin", String.class, String.class, String.class);
			for(QueryJoin jf : joins){
				visitor.visitVarInsn(ALOAD, 1);
				visitor.visitVarInsn(ALOAD, fieldPoint);
				visitor.visitLdcInsn(jf.value());
				visitor.visitLdcInsn(jf.alias());
				visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "internalJoin", ijdes, true);
				visitor.visitVarInsn(ASTORE, fieldPoint);
			}
		}
		visitor.visitVarInsn(ALOAD, 1);
		visitor.visitVarInsn(ALOAD, fieldPoint);
		visitor.visitLdcInsn(propertyName);
		visitor.visitLdcInsn(opt.getOperator());
		visitor.visitVarInsn(ALOAD, valuePoint);
		visitor.visitMethodInsn(INVOKEINTERFACE, parserDes, "internalAnd", iades, true);
	}

	/**
	 * primitive to object
	 * 
	 * @param visitor
	 * @param getter
	 * @param getterDes
	 * @param qt
	 */
	private void _p2o(MethodVisitor visitor, Method getter, String getterDes, Class<?> qt, int valuePoint) {
		visitor.visitVarInsn(ALOAD, 3);
		visitor.visitMethodInsn(INVOKEVIRTUAL, objectPath, getter.getName(), getterDes, false);
		if(int.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
		}else if(long.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
		}else if(double.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
		}else if(float.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
		}else if(boolean.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
		}else if(short.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
		}else if(byte.class.equals(qt)){
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
		}else{//char
			visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
		}
		visitor.visitVarInsn(ASTORE, valuePoint);
	}

	private void _string(MethodVisitor visitor, String getterDes, String ll, String rl, int valuePoint) {
		visitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
		visitor.visitInsn(DUP);
		if(StringHelper.isNotEmpty(ll)){
			visitor.visitLdcInsn(ll);
			visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
		}else{
			visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
		}
		visitor.visitVarInsn(ALOAD, valuePoint);
		append.visitMethodInsn(visitor, INVOKEVIRTUAL, false);
		if(StringHelper.isNotEmpty(rl)){
			visitor.visitLdcInsn(rl);
			append.visitMethodInsn(visitor, INVOKEVIRTUAL, false);
		}
		visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
		visitor.visitVarInsn(ASTORE, valuePoint);
	}

	private String _0(String name, int plen){
		int len = name.length() - plen;
		if(len > 0){
			char[] chs = name.toCharArray();
			if(Character.isUpperCase(chs[plen])){
				chs[plen] = Character.toLowerCase(chs[plen]);
				return new String(chs, plen, len);
			}
		}
		return null;
	}
}
